## Civcraft: SC Mod Combat Python Utilities

import CvUtil
from CvPythonExtensions import *

# globals
gc = CyGlobalContext()

# Lambda function for checking if pointers are valid.
isNotValid = lambda x: not x or x.isNone()

# Each of the following attack functions return boolean True if any combat
# did occur, boolean False otherwise.

#
# Single attack
#
def singleAttack(pAttackUnit, pDefendPlot):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendPlot):
        print "Not valid defender"
        return False

    pTargetUnit = pDefendPlot.getBestDefenderSC(pAttackUnit, False)
    if isNotValid(pTargetUnit):
        print "No valid target found"
        return False

    combat(pAttackUnit, pTargetUnit)
    return True

#
# Single attack Targetted
#
def singleAttackTargetted(pAttackUnit, pDefendUnit):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendUnit):
        print "Not valid defender"
        return False

    if not pAttackUnit.canSCAttackUnit(pDefendUnit, False):
        print "Cannot attack"
        return False

    combat(pAttackUnit, pDefendUnit)
    return True

#
# Turn Attack
#
def turnAttack(pAttackUnit, pDefendPlot):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendPlot):
        print "Not valid defender"
        return False

    bCombat = False
    while pAttackUnit.hasMoreAttacksLeft():
        pTargetUnit = pDefendPlot.getBestDefenderSC(pAttackUnit, False)
        if isNotValid(pTargetUnit):
            print "No valid target found"
            break

        combat(pAttackUnit, pTargetUnit)
        bCombat = True

    return bCombat

#
# Turn attack Targetted
#
def turnAttackTargetted(pAttackUnit, pDefendUnit):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendUnit):
        print "Not valid defender"
        return False

    if not pAttackUnit.canSCAttackUnit(pDefendUnit, False):
        print "Cannot attack"
        return False

    bCombat = False
    while pAttackUnit.hasMoreAttacksLeft() and not pDefendUnit.isDead():
        combat(pAttackUnit, pDefendUnit)
        bCombat = True

    return bCombat

#
# Single Lurker attack
#
def singleLurkerAttack(pAttackUnit, pDefendPlot):
    if isNotValid(pAttackUnit):
        print "Not valid attacker"
        return False

    if isNotValid(pDefendPlot):
        print "Not valid defender"
        return False

    for iUnit in range(pDefendPlot.getNumUnits()):
        pTargetUnit = pDefendPlot.getUnit(iUnit)
        if pAttackUnit.canSCAttackUnit(pTargetUnit, True):
            combat(pAttackUnit, pTargetUnit, False)

    return True


#
# Goes through everything combat does, from costing the attackers attack points,
# to dealing damage and killing possible cities whose buildings died. Much of this is
# delegated to functions below.
#
def combat(pAttackUnit, pDefendUnit, bDecrementMoves = True):
    if bDecrementMoves:
        pAttackUnit.changeAttacks(1) # Cost them one attack
        pAttackUnit.changeMoves(gc.getMOVE_DENOMINATOR()) # Cost them one move

    # Some attackers might use their misc ammo as the actual attacker.
    pRealAttackers = getRealAttackers(pAttackUnit)


    for pLoopAttacker in pRealAttackers:
        iDamage = getUnitDamage(pLoopAttacker, pDefendUnit)

        #
        # Range Nullified
        # A unit is not damaged by range damage if it's inside a plot
        # that has an attachment of the Range Nullified variety (like Dark Swarm)
        # Note that splash damage still is done in full, but this one unit is
        # untouched.
        # Also, buildings are always affected.
        #
        if not gc.getUnitInfo(pDefendUnit.getUnitType()).isBuilding():
            if isAttackerRanged(pLoopAttacker, pDefendUnit):
                for iI in range(gc.getNumPlotAttachmentInfos()):
                    if gc.getPlotAttachmentInfo(iI).isRangeDamageNullified():
                        if pDefendUnit.plot().getTotalNumPlotAttachments(iI) > 0:
                            iDamage = 0
                            break
                

        # Check for damage versus this unit
        pDefendUnit.doDamage(iDamage, pLoopAttacker.getOwner())

        # Attach spore to all remaining air units if the attacking units, if necessary
        attachSpores(pLoopAttacker, pDefendUnit.plot())

        doSplashDamage(pLoopAttacker, pDefendUnit)

        CvUtil.pyPrint("Player %ds %s attacked player %ds %s for %d damage." %
                       (pLoopAttacker.getOwner(), pLoopAttacker.getName(),
                        pDefendUnit.getOwner(), pDefendUnit.getName(),
                        iDamage)
                       )

        if (gc.getUnitInfo(pLoopAttacker.getUnitType()).isKamikazeAttack()):
            pLoopAttacker.kill(True, PlayerTypes.NO_PLAYER)
            CvUtil.pyPrint("Killing kamikaze attacker")

    # After all the attackers have attacked, see if they were actually ammo units, if used.
    changeOriginalAttackersAmmo(pAttackUnit, pRealAttackers)

    return

#
# Determines if the attacker is a ranged attacker.
#
def isAttackerRanged(pAttackUnit, pDefendUnit):
    bAttackIsRanged = False
    
    if pDefendUnit.getDomainType() == DomainTypes.DOMAIN_LAND:
        if pAttackUnit.getGroundRange() > 1:
            bAttackIsRanged = True
    elif pDefendUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT:
        if pAttackUnit.getAirRange() > 1:
            bAttackIsRanged = True

    return bAttackIsRanged

#
# Determines the real attacker from an attacker.
# An attacker might actually use units that are part of their ammo to attack with.
# Although most of the time, this just returns a list with only the attacker in it, it might return
# a list of new, init'd attackers that were just made up since they're "ammo".
#

def getRealAttackers(pAttackUnit):
    pRealAttackers = []
    pAttackInfo = gc.getUnitInfo( pAttackUnit.getUnitType() )
    pAttackPlayer = gc.getPlayer( pAttackUnit.getOwner() )
    
    # Most units don't use their misc ammo in attack, so the list of attackers will just be them.
    if (not pAttackInfo.isMiscAmmoUsedInAttack()):
        pRealAttackers.append(pAttackUnit)
    else:
        eMiscAmmoUnitClassType = pAttackInfo.getMiscAmmoUnitClassType()
        eUnitType = gc.getCivilizationInfo(gc.getPlayer(pAttackUnit.getOwner()).getCivilizationType()).getCivilizationUnits(eMiscAmmoUnitClassType)

        # Make one new unit from the ammo.
        iUnitsToMake = min( pAttackInfo.getMaxMiscAmmoUsedInAttack(), pAttackUnit.getMiscAmmo() )
        for iUnit in range( iUnitsToMake ):
            pRealAttackers.append( pAttackPlayer.initUnit( eUnitType, pAttackUnit.getX(), pAttackUnit.getY(), UnitAITypes.NO_UNITAI ) )
        
    CvUtil.pyPrint("List for real attackers: %s" % str(pRealAttackers))
    return pRealAttackers

#
# Determines if the real attackers used in the attack were just "ammo".
# If so, check if any are dead, and if they are, removed them from the original attackers ammo.
#

def changeOriginalAttackersAmmo(pOriginalAttackerUnit, pRealAttackers):
    CvUtil.pyPrint("Change original attackers ammo")
    pAttackInfo = gc.getUnitInfo( pOriginalAttackerUnit.getUnitType() )
    
    # Most units didn't use the ammo for the attack, so there's nothing to do here.
    if (not pAttackInfo.isMiscAmmoUsedInAttack()):
        return
    else:
        # For each ammo used in the attack, see if the ammo died during combat. If so, remove one ammo from the player.
        for pAmmoUnit in pRealAttackers:
            if pAmmoUnit.isDead() or pAmmoUnit.isDelayedDeath():
                CvUtil.pyPrint("Ammo unit died. Changing ammo")
                pOriginalAttackerUnit.changeMiscAmmo(-1)
            else:
                CvUtil.pyPrint("Ammo unit not dead. Just killing")
                # The unit, not being dead, must be killed (because it's not supposed to stay on the map
                pAmmoUnit.kill(False, PlayerTypes.NO_PLAYER)

    CvUtil.pyPrint("Done changing original attackers ammo.")
            
            
    

#
# Determines the attack range needed for a unit to attack
# another unit. Basically, returns either the groundRange
# or airRange, dependent upon if the defending unit is a 
# ground or air unit
#
def determineAttackRangeNeeded(pAttackUnit, pDefendUnit):
    if (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_LAND):
        return pDefendUnit.getGroundRange()
    elif (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT):
        return pDefendUnit.getAirRange()
    else:
        CvUtil.pyAssert(False, "In determineAttackRangeNeeded, unit has unknown domain type.")

#
# Get all enemy units of team eEnemyOfTeam that are on the plot pPlot
#
def getEnemyUnitsFromPlot(pPlot, eEnemyOfTeam):
    pUnits = []
    pTeam = gc.getTeam(eEnemyOfTeam)
    for iUnit in range(0, pPlot.getNumUnits()):
        pUnit = pPlot.getUnit(iUnit)
        if pTeam.isAtWar(pUnit.getTeam()):
            pUnits.append(pUnit)

    # GERIKESTODO: Randomize this unit list
    return pUnits

#
# Gets iNum random units, starting with the selected plot, and if not finding them there, searches adjacent
# plots  for more units. Once it finds iNum units, returns the units in a list. The units must be enemies of
# team eEnemyOfTeam.
#
def getRandomUnitsFromPlot(pPlot, iNum, eEnemyOfTeam):
    pUnits = []
    pUnits.extend(getEnemyUnitsFromPlot(pPlot, eEnemyOfTeam) )
    if len(pUnits) < iNum:
        iCenterPlotX = pPlot.getX()
        iCenterPlotY = pPlot.getY()

        # Make a list of numbers, each one representing an offset.
        #  6     7      8
        #  3            5
        #  0     1      2
        # GERIKESTODO: make this pick a random adjacent plot.
        pOffsetList = [0,1,2,3,5,6,7,8]
        iIndex = 0

        while (len(pUnits) < iNum) and (iIndex < len(pOffsetList)):
            iPlotX = iCenterPlotX - 1 + (pOffsetList[iIndex] % 3)
            iPlotY = iCenterPlotY - 1 + (pOffsetList[iIndex] / 3)
            pLoopPlot = CyMap().plot(iPlotX, iPlotY)
            pUnits.extend(getEnemyUnitsFromPlot(pLoopPlot, eEnemyOfTeam))
            iIndex += 1

    # Only return the first iNum units, incase we found more.
    CvUtil.pyPrint("Returning %s" % pUnits)
    if len(pUnits) > iNum:
        return pUnits[:iNum]
    else:
        return pUnits
            
            
#
# Does splash damage to units that need splashing done to them.
# For each unit that is killed by Splash damage, the unit's instance
# is added to a list. This list is returned.
#
def doSplashDamage(pAttackUnit, pDefendUnit):
    pAttackInfo = gc.getUnitInfo(pAttackUnit.getUnitType())
    pDefendInfo = gc.getUnitInfo(pDefendUnit.getUnitType())
    pDefenderPlot = pDefendUnit.plot()

    if pAttackUnit.isInSiegeMode():
        bGroundSmallSplash = pAttackInfo.isSiegeModeGroundSmallSplashAttack()
        bGroundSplash = pAttackInfo.isSiegeModeGroundSplashAttack()
        bGroundLargeSplash = pAttackInfo.isSiegeModeGroundLargeSplashAttack()
    else:
        bAirSmallSplash = pAttackInfo.isGroundSmallSplashAttack()
        bAirSplash = pAttackInfo.isGroundSplashAttack()
        bAirLargeSplash = pAttackInfo.isGroundLargeSplashAttack()

    CvUtil.pyPrint("Trying to do splash damage")

    if (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_LAND):
        if (bGroundSmallSplash):
            iMaxSplashes = gc.getDefineINT("SMALL_SPLASH_MAX_SPLASHES")
            iDegradeDenominator = gc.getDefineINT("SMALL_SPLASH_DEGRADE_DENOMINATOR_MULTIPLIER")
            iCurrentDegradeDenominator = gc.getDefineINT("SMALL_SPLASH_DEGRADE_DENOMINATOR_START")

            if iCurrentDegradeDenominator == 0:
                iCurrentDegradeDenominator = 1 # Avoid divide-by-zero

            # Get enough units to "splash" damage to, plus one more, incase we recieve the pDefendUnit also.
            pAffectedUnits = getRandomUnitsFromPlot(pDefenderPlot, iMaxSplashes + 1, pAttackUnit.getTeam())

            # See if we can find the defending unit, and remove it.
            # If we can't find it, remove one of the other ones instead, since otherwise we'll have too many.
            pDefendUnitID = pDefendUnit.getID()
            pDefendUnitOWNER = pDefendUnit.getOwner()
            
            bFoundDefender = False
            for pLoopUnit in pAffectedUnits:
                if pLoopUnit.getID() == pDefendUnitID and pLoopUnit.getOwner() == pDefendUnitOWNER:
                    pAffectedUnits.remove(pLoopUnit)
                    bFoundDefender = True
                    break

            if not bFoundDefender:
                pAffectedUnits.pop()

            # Get the splashes that will occur, and do the damage to each.
            for iI in range(len(pAffectedUnits)):
                pLoopUnit = pAffectedUnits[iI]
                if pLoopUnit.getDomainType() == DomainTypes.DOMAIN_LAND:
                    iBaseDamage = pAttackUnit.getGroundDamage()
                elif pLoopUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT:
                    iBaseDamage = pAttackUnit.getAirDamage()
                else:
                    iBaseDamage = 0

                iDamage = getFinalDamageAfterModifiers(float(iBaseDamage), pAttackInfo.isGroundExplosiveAttack(), pAttackInfo.isGroundConcussiveOrPlasmaAttack(), pDefendUnit)
                CvUtil.pyPrint("iDamage before degrade: %d, degrade: %d" % (iDamage, iCurrentDegradeDenominator))
                iDamage = max(1, int(round(iDamage * float(1.0/float(iCurrentDegradeDenominator)))) )
                CvUtil.pyPrint("Small splash damage from player %d's %s done to player %d %s's for %d damage" % (pAttackUnit.getOwner(), pAttackUnit.getName(), pDefendUnit.getOwner(), pDefendUnit.getName(), iDamage) )
                pLoopUnit.doDamage(iDamage, pAttackUnit.getOwner() )
                iCurrentDegradeDenominator *= iDegradeDenominator
            
        if (bGroundSplash):
            for iI in range(pDefenderPlot.getNumUnits()):
                pLoopUnit = pDefenderPlot.getUnit(iI)
                if (pLoopUnit.getID() != pDefendUnit.getID() and pLoopUnit.getDomainType() == DomainTypes.DOMAIN_LAND):
                    pLoopUnit.doDamage(getUnitDamage(pAttackUnit, pLoopUnit) * gc.getDefineFLOAT("SPLASH_DAMAGE_MULTIPLIER"), pAttackUnit.getOwner() )

        if (bGroundLargeSplash):
            # Go through each adjacent tile:
            for iX in range(-1, 2,1):
                for iY in range(-1,2,1):
                    pPlot = CyMap().plot(pDefenderPlot.getX() + iX, pDefenderPlot.getY() + iY)

                    # In each tile, go through each unit affected by the splash (not counting that actual unit attacked)
                    for iZ in range(pPlot.getNumUnits()):
                        pLoopUnit = pPlot.getUnit(iZ)
                        if (pLoopUnit.getID() != pDefendUnit.getID() and
                            pLoopUnit.getDomainType() == DomainTypes.DOMAIN_LAND):

                            # If this is the spot of the actual attack...
                            if (iX == iY == 0):
                                pLoopUnit.doDamage(int(round(getUnitDamage(pAttackUnit, pLoopUnit) * gc.getDefineFLOAT("LARGE_SPLASH_DAMAGE_SAME_TILE_MULTIPLIER"))),
                                                   pAttackUnit.getOwner())
                            # If this was an adjacent plot...
                            else:
                                pLoopUnit.doDamage(int(round(getUnitDamage(pAttackUnit, pLoopUnit) * gc.getDefineFLOAT("LARGE_SPLASH_DAMAGE_ADJACENT_TILE_MULTIPLIER"))),
                                                   pAttackUnit.getOwner())

                                                   
                            


    elif (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT):
        if (bAirSmallSplash):
            iMaxSplashes = gc.getDefineINT("SMALL_SPLASH_MAX_SPLASHES")
            iDegradeDenominator = gc.getDefineINT("SMALL_SPLASH_DEGRADE_DENOMINATOR_MULTIPLIER")
            iCurrentDegradeDenominator = gc.getDefineINT("SMALL_SPLASH_DEGRADE_DENOMINATOR_START")

            if iCurrentDegradeDenominator == 0:
                iCurrentDegradeDenominator = 1 # Avoid divide-by-zero

            # Get enough units to "splash" damage to, plus one more, incase we recieve the pDefendUnit also.
            pAffectedUnits = getRandomUnitsFromPlot(pDefenderPlot, iMaxSplashes + 1, pAttackUnit.getTeam())

            # See if we can find the defending unit, and remove it.
            # If we can't find it, remove one of the other ones instead, since otherwise we'll have too many.
            pDefendUnitID = pDefendUnit.getID()
            pDefendUnitOWNER = pDefendUnit.getOwner()
            
            bFoundDefender = False
            for pLoopUnit in pAffectedUnits:
                if pLoopUnit.getID() == pDefendUnitID and pLoopUnit.getOwner() == pDefendUnitOWNER:
                    pAffectedUnits.remove(pLoopUnit)
                    bFoundDefender = True
                    break

            if not bFoundDefender:
                pAffectedUnits.pop()

            # Get the splashes that will occur, and do the damage to each.
            for iI in range(len(pAffectedUnits)):
                pLoopUnit = pAffectedUnits[iI]
                if pLoopUnit.getDomainType() == DomainTypes.DOMAIN_LAND:
                    iBaseDamage = pAttackUnit.getGroundDamage()
                elif pLoopUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT:
                    iBaseDamage = pAttackUnit.getAirDamage()
                else:
                    iBaseDamage = 0

                iDamage = getFinalDamageAfterModifiers(float(iBaseDamage), pAttackInfo.isAirExplosiveAttack(), pAttackInfo.isAirConcussiveOrPlasmaAttack(), pDefendUnit)
                CvUtil.pyPrint("iDamage before degrade: %d, degrade: %d" % (iDamage, iCurrentDegradeDenominator))
                iDamage = max(1, int(round(iDamage * float(1.0/float(iCurrentDegradeDenominator)))) )
                CvUtil.pyPrint("Small splash damage from player %d's %s done to player %d %s's for %d damage" % (pAttackUnit.getOwner(), pAttackUnit.getName(), pDefendUnit.getOwner(), pDefendUnit.getName(), iDamage) )
                pLoopUnit.doDamage(iDamage, pAttackUnit.getOwner() )
                iCurrentDegradeDenominator *= iDegradeDenominator
            


#
# Attaches a spore to each
def attachSpores(pAttackUnit, pDefendPlot):
    if (pAttackUnit.getUnitType() == gc.getInfoTypeForString("UNIT_DEVOURER") ):
        eAcidSporeAttachmentType = gc.getInfoTypeForString("UNIT_ATTACHMENT_ACID_SPORE")

        pUnitList = []
        iNumUnits = pDefendPlot.getNumUnits()
        for iI in range(0, iNumUnits):
            pUnitList.append( pDefendPlot.getUnit(iI) )

        for pLoopUnit in pUnitList:
            if (pLoopUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT):
                pLoopUnit.attachUnitAttachment(pAttackUnit.getOwner(), eAcidSporeAttachmentType)


#
# Returns the number of acid spores on a unit.
#
def getSporeDamage(pUnit):
    DAMAGE_PER_SPORE = 1
    eAcidSporeUnitAttachment = gc.getInfoTypeForString("UNIT_ATTACHMENT_ACID_SPORE")
    return pUnit.getTotalNumUnitAttachments(eAcidSporeUnitAttachment) * DAMAGE_PER_SPORE

#
# Gets the damage one unit does to another, checking for all modifiers
#
def getUnitDamage(pAttackUnit, pDefendUnit):
    if isNotValid(pAttackUnit):
        return 0

    if isNotValid(pDefendUnit):
        return 0

    if (pDefendUnit.isInvulnerable()):
        return 0

    # If this unit is a hallucination, don't let it deal any damage.
    if (pAttackUnit.getTotalNumUnitAttachments(gc.getInfoTypeForString("UNIT_ATTACHMENT_HALLUCINATION")) > 0):
        return 0

    pAttackInfo = gc.getUnitInfo(pAttackUnit.getUnitType())


    if (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_LAND):
        iDamage = getFinalDamageAfterModifiers(pAttackUnit.getGroundDamage(),
                                               pAttackInfo.isGroundExplosiveAttack(),
                                               pAttackInfo.isGroundConcussiveOrPlasmaAttack(),
                                               pDefendUnit)
    elif (pDefendUnit.getDomainType() == DomainTypes.DOMAIN_FLOAT):
         iDamage = getFinalDamageAfterModifiers(pAttackUnit.getGroundDamage(),
                                               pAttackInfo.isAirExplosiveAttack(),
                                               pAttackInfo.isAirConcussiveOrPlasmaAttack(),
                                               pDefendUnit)

    return iDamage


    

# Takes in the damage that a unit does, and calculates, using the shields, defensive matrix's, modifiers, etc. what actual damage will be done.
def getFinalDamageAfterModifiers(fBaseDamage, bExplosiveAttack, bConcussiveOrPlasmaAttack, pDefendUnit):
    fDamageDoneToShields = 0.0
    fDamageDoneToDefensiveMatrix = 0.0
    pDefendInfo = gc.getUnitInfo(pDefendUnit.getUnitType())

    fDamage = fBaseDamage

    # Spores
    fDamage += getSporeDamage(pDefendUnit)
    
    # Do full damage to the defensive matrix.
    if (pDefendUnit.getDefensiveMatrixShield() >= fDamage):
        return fDamage
    fDamageDoneToDefensiveMatrix = pDefendUnit.getDefensiveMatrixShield()
    fDamage -= fDamageDoneToDefensiveMatrix

    # Remove the damage taken by the shield armor if the unit has shields 
    if (pDefendUnit.currShields() > 0):
        fDamage -= pDefendUnit.getShieldArmor()

    # Damage done to protoss shields are always full, 
    if (pDefendUnit.currShields() >= fDamage):
        return fDamage

    # Determine how much damage is automatically going to be done to the shields, and subtract that from the total.
    if (pDefendUnit.currShields() > 0):
        fDamageDoneToShields += pDefendUnit.currShields()
        fDamage -= fDamageDoneToShields

    fDamage -= pDefendUnit.getArmor()
    
    # fDamage should now hold how much damage to do to the rest of the unit, after subtracting shields and armor

    # For any damage done to the unit's HP, determine modifiers
    if (bExplosiveAttack):
        if (pDefendInfo.isSmallSize()):
            fDamage *= gc.getDefineFLOAT("EXPLOSIVE_DAMAGE_VS_SMALL_UNIT_MULTIPLIER")
        elif (pDefendInfo.isMediumSize()):
            fDamage *= gc.getDefineFLOAT("EXPLOSIVE_DAMAGE_VS_MEDIUM_UNIT_MULTIPLIER")
    if (bConcussiveOrPlasmaAttack):
        if (pDefendInfo.isMediumSize()):
            fDamage *= gc.getDefineFLOAT("CONCUSSIVE_OR_PLASMA_DAMAGE_VS_MEDIUM_UNIT_MULTIPLIER")
        elif (pDefendInfo.isLargeSize()):
            fDamage *= gc.getDefineFLOAT("CONCUSSIVE_OR_PLASMA_DAMAGE_VS_LARGE_UNIT_MULTIPLIER")

    # Return the modified damage to HP plus the damage that was done to the shield as one value.
    # The doDamage function in the SDK will correctly subtract the damage from each.
    # Note that every attack will always do at least one point of damage.
    return max(1, int(round(fDamage + fDamageDoneToShields + fDamageDoneToDefensiveMatrix))) 
        
        
    
    
